package com.jinrustar.httpfileserver.core.http;


import com.alibaba.fastjson.JSON;
import com.jinrustar.httpfileserver.core.HttpFileServerApp;
import com.jinrustar.httpfileserver.springboot.Application;
import com.jinrustar.httpfileserver.util.CommonUtil;
import com.jinrustar.httpfileserver.util.FileUtil;
import com.jinrustar.httpfileserver.core.support.Assert;
import com.jinrustar.httpfileserver.core.util.InstanceUtil;
import com.jinrustar.httpfileserver.core.ws.WSClient;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.*;
import io.netty.handler.stream.ChunkedFile;
import io.netty.util.CharsetUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.activation.MimetypesFileTypeMap;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.regex.Pattern;

import static io.netty.buffer.Unpooled.copiedBuffer;
import static io.netty.handler.codec.http.HttpHeaderUtil.isTransferEncodingChunked;
import static io.netty.handler.codec.http.HttpMethod.GET;
import static io.netty.handler.codec.http.HttpMethod.POST;

public class HttpFileServerHandler extends SimpleChannelInboundHandler<HttpObject> {
    private static final Logger logger = LoggerFactory.getLogger(HttpFileServerHandler.class);

    /*根目录*/
    private final String dir;

    /*页面html*/
    private static String pageHtml;

    /*   *//*websocket client*//*
    private static WSClient wsClient; *//*等待实例化*/

    /*总的数据传输大小*/
    private static long totalDataSize;

    /*总的耗费时间*/
    private static long totalTakeTime;

    /*最高传输速度mb/s*/
    private static double serverMaxTransferSpeed;

    /*最高平均速度mb/s(每个文件下载完成时的平均速度)*/
    private static double serverMaxAvgTransferSpeed;

    public HttpFileServerHandler(String dir) {
        this.dir = dir;
    }

 /*   public static void setWsClient(WSClient wsClient) {
        HttpFileServerHandler.wsClient = wsClient;
    }*/

    @Override
    protected void messageReceived(ChannelHandlerContext ctx, HttpObject httpObject) throws Exception {
        System.out.println("###message received.." + httpObject);

        /*if (httpObject instanceof HttpRequest) { *//*普通request*//*
            doRequest(ctx, (FullHttpRequest) httpObject);
        }*/

        doRequest(ctx, (FullHttpRequest) httpObject);
// else if (httpObject instanceof HttpContent) {/*上传文件*/
//            System.out.println("###上传文件");
//            if (decoder != null && hasUploadData) {
//                HttpContent chunk = (HttpContent) httpObject;
//                try {
//                    decoder.offer(chunk);
//                } catch (HttpPostRequestDecoder.ErrorDataDecoderException e1) {
//                    writeResponseString(ctx, httpObject.toString());
//                    reset();
//                    ctx.channel().close();
//                    return;
//                }
//                readHttpDataChunkByChunk(); /*从解码器decoder中读出数据*/
//                if (chunk instanceof LastHttpContent) {
//                    writeResponseString(ctx, "{\"code\":0,\"msg\":\"ok\"}");
//                    hasUploadData = false;
//                    reset();
//                }
//            } else {
//                writeResponseString(ctx, httpObject.toString());
//                ctx.channel().close();
//                return;
//            }
//        }

    }

    /*2017.12.4 上传文件*/
    private static final HttpDataFactory factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE); // Disk if size exceed
    private HttpPostRequestDecoder decoder;
    private boolean hasUploadData = false;

    private void doRequest(ChannelHandlerContext ctx, FullHttpRequest request) throws IOException {

        logger.info("##Netty request uri:{}", request.uri());

        Map<String, String> requestParams = getRequestParams(ctx, request);

        logger.info("##Netty request parameters:{}", requestParams);

        if (request.method() == GET) {
            /*QueryStringDecoder decoderQuery = new QueryStringDecoder(request.uri());
            Map<String, List<String>> uriAttributes = decoderQuery.parameters();
            Map<String, String> queryParameters = InstanceUtil.newHashMap();
            for (Map.Entry<String, List<String>> attr : uriAttributes.entrySet()) {
                queryParameters.put(attr.getKey(), Arrays.toString(attr.getValue().toArray()));
            }
            logger.info("##queryParameters:{}={}", queryParameters);

            if (!request.decoderResult().isSuccess()) {
                sendError(ctx, HttpResponseStatus.BAD_REQUEST);
                return;
            }*/

            String path = requestParams.get("path");

            path = path == null ? "" : path.replaceAll("\\[", "%5B").replaceAll("\\]", "%5D");/*处理中括号(浏览器未处理)*/

            /*URI uri = new URI(uriStr);
            logger.info("##path:{}", URLDecoder.decode(uri.getPath()));
            logger.info("##uri:{}", uri);
            String path = uri.getPath();*/

            if (path.equals("/qrcode")) {
                doQrcode(ctx, request);
                return;
            }

            if (path.startsWith("http://") || path.startsWith("//")) {
                logger.debug("##http:{}", path);
                /*ctx.writeAndFlush(FileUtil.read(new URL(path).openConnection().getInputStream()));*/
                sendRedirect(ctx, path);/*使用浏览器重定向到原始地址下载*/
                return;
            }

            path = sanitizeUri(path);

            File targetFile = new File(path);

            System.out.println("##targetFile:" + targetFile);

            Object userNameObj = requestParams.get("u");
            /*Assert.notNull(userName);*/

            Object avatarObj = requestParams.get("a");
            /*Assert.notNull(avatar);*/

            Object userIdObj = requestParams.get("i");
            /*Assert.notNull(userId);*/

            Object signObj = requestParams.get("_s");
            /*Assert.notNull(sign);*/

            if (targetFile.isFile()) { /*临时测试, 只有文件才需要以下参数（识别身份）*/
                /*todo 检查签名*/
            }

            /*if (path == null) {
                logger.error("## path is null");
                sendError(ctx, HttpResponseStatus.FORBIDDEN);
                return;
            }*/

            logger.info("## sanitizeUri path {}", path);

            /*File file = new File(path *//*+ "/"*//*);*/

            File file = targetFile;

            System.out.println("##targetPath:" + file);

            /*if (file.isHidden() || !file.exists()) {
                sendError(ctx, HttpResponseStatus.NOT_FOUND, file.getPath());
                return;
            }*/
            if (file.isDirectory()) {
                sendListing(ctx, file);
                return;
            }
            if (!file.isFile()) {
                sendError(ctx, HttpResponseStatus.FORBIDDEN);
                return;
            }

            RandomAccessFile randomAccessFile;
            try {
                randomAccessFile = new RandomAccessFile(file, "r");
            } catch (FileNotFoundException fnfe) {
                sendError(ctx, HttpResponseStatus.NOT_FOUND);
                return;
            }

            long fileLength = randomAccessFile.length();
            HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
            HttpHeaderUtil.setContentLength(response, fileLength);
            setContentTypeHeader(response, file);

            if (HttpHeaderUtil.isKeepAlive(request)) {
                response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
            }

            logger.info("###response headers:{}", response.headers());

            ctx.write(response);
            ChannelFuture sendFileFuture;
            sendFileFuture = ctx.write(new ChunkedFile(randomAccessFile, 0, fileLength, 8192), ctx.newProgressivePromise());


            /*下载开始时间*/
            long downloadStartTime = System.currentTimeMillis();
            long[] totalSize = {0}; /*记录文件总大小*/
            double[] maxAvgDownloadSpeed = {0}; /*记录最高平均速度*/

            String userName = userNameObj != null ? userNameObj.toString() : "default";
            String avatar = avatarObj != null ? avatarObj.toString() : "http://wx.qlogo.cn/mmopen/vi_32/wcibeaMUKKBnPSR8ic9GJwC0IC3FLqFXh6bEh4ia7COVicic3FX9es7AkR1kyg0hwu1TYODpotTeTEARjxWpX4lytMw/0";
            String userId = userIdObj != null ? userIdObj.toString() : "820491902842318848";

            int[] lastPercent = {0};

            /*进度*/
            DownloadProgress downloadProgress = new DownloadProgress();
            /*文件下载日志*/
            DownloadFileLog downloadFileLog = new DownloadFileLog();

            sendFileFuture.addListener(new ChannelProgressiveFutureListener() {

                @Override
                public void operationComplete(ChannelProgressiveFuture future) throws Exception {
                    logger.info("Transfer complete.");

                    /*计算平均速度*/
                    long takeTime = System.currentTimeMillis() - downloadStartTime;
                    if (takeTime < 1) { /*不足一毫秒*/
                        takeTime = 1; //java.lang.ArithmeticException: / by zero
                    }
                    double avgDownloadSpeed = (totalSize[0] / takeTime) / 1024.0 / 1024.0; /*平均每毫秒下载速度mb*/
                    avgDownloadSpeed *= 1000;/*每秒下载速度mb*/


                    CommonUtil.notify("", "传输完成," + targetFile.getName());

                    /*经测试，无法显示通知,可能是内容太长*/
                   /* String c = String.format("文件总大小:%s bytes, 耗时:%sms, 平均速度：%smb/s, 最高平均速度：%smb/s", totalSize[0], takeTime, avgDownloadSpeed, maxAvgDownloadSpeed[0]);
                    CommonUtil.notify("",c);*/

                    logger.info("##文件总大小:{}bytes, 耗时:{}ms, 平均速度：{}mb/s, 最高平均速度：{}mb/s", totalSize[0], takeTime, avgDownloadSpeed, maxAvgDownloadSpeed[0]);
                    if (avgDownloadSpeed > serverMaxAvgTransferSpeed) {
                        serverMaxAvgTransferSpeed = avgDownloadSpeed;/*更新服务器最高平均传输速度*/
                    }

                    /*存入数据库*/
                    if (userId != null) {
                        downloadFileLog.userId = userId;
                        downloadFileLog.userAvatar = avatar;
                        downloadFileLog.userName = userName;
                        downloadFileLog.fileName = targetFile.getName();
                        downloadFileLog.startTime = downloadStartTime;
                        downloadFileLog.totalDataSize = totalSize[0];
                        downloadFileLog.avgDownloadSpeed = CommonUtil.formatDoubleWithROUND_HALF_UP(2, avgDownloadSpeed);
                        downloadFileLog.takeTime = takeTime;

                        downloadFileLog.maxDownloadSpeed = maxAvgDownloadSpeed[0];/*最大平均下载速度*/

                       /* try {
                            com.jinrustar.httpfileserver.util.RedisUtil.getJedis().set("USER::" + userId + "::" + targetFile.getName(), JSON.toJSONString(downloadFileLog));
                        } catch (Exception ex) {
                            //ex.printStackTrace();
                        }*/

                        HttpFileServerHandler.this.sendDownloadComplete(downloadFileLog);
                    }

                    /*服务器总耗时增加*/
                    totalTakeTime += takeTime;
                    /*服务器总下载数据流量增加*/
                    totalDataSize += totalSize[0];

                    ServerData serverData = new ServerData();
                    serverData.totalDataSize = CommonUtil.formatDoubleWithROUND_HALF_UP(5, totalDataSize / 1024.0 / 1024.0 / 1024.0);
                    serverData.totalTakeTime = totalTakeTime / 1000;
                    double serverAvgTransferSpeed = (totalDataSize / totalTakeTime) / 1024.0 / 1024.0; /*平均每毫秒下载速度mb/ms*/
                    serverAvgTransferSpeed *= 1000;/*每秒下载速度mb/s*/
                    serverData.avgTransferSpeed = CommonUtil.formatDoubleWithROUND_HALF_UP(5, serverAvgTransferSpeed);
                    serverData.maxTransferSpeed = serverMaxTransferSpeed; /*最高传输速度*/

                    serverData.maxAvgTransferSpeed = serverAvgTransferSpeed;

                    logger.info("##服务器总数据量:{}GB,总耗时:{}s,总平均速度：{}mb/s, 最高平均速度:{}mb/s, 理论最高速度：{}mb/s", serverData.totalDataSize, serverData.totalTakeTime, serverData.avgTransferSpeed, serverData.maxAvgTransferSpeed, serverData.maxTransferSpeed);

                    CommonUtil.notify("提示", String.format("##服务器总数据量:%sGB,总耗时:%ss,总平均速度：%smb/s, 最高平均速度:%smb/s, 理论最高速度：%smb/s", serverData.totalDataSize, serverData.totalTakeTime, serverData.avgTransferSpeed, serverData.maxAvgTransferSpeed, serverData.maxTransferSpeed));

                    HttpFileServerHandler.this.sendServerData(serverData);
                }

                @Override
                public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) {
                    /*计算下载速度*/
                    int percent = (int) (progress * 1.0 / total * 100);
                /*if (total < 0) {
                    logger.info("Transfer progress: " + progress);
                } else {
                    logger.info("{}, Transfer progress: {},percent:{}%", future.channel().id(), progress + "/" + total, percent);
                }*/

                    if (userId != null) {
                        /**/
                        long currentTakeTime = System.currentTimeMillis() - downloadStartTime;
                        if (currentTakeTime > 0) {
                            downloadProgress.userId = userId;
                            downloadProgress.userName = userName;
                            downloadProgress.userAvatar = avatar;
                            downloadProgress.fileName = targetFile.getName();
                            downloadProgress.progressDataSize = progress;
                            downloadProgress.totalDataSize = total;
                            downloadProgress.percent = (progress * 1.0 / total * 100);
                            downloadProgress.startTime = downloadStartTime;
                            downloadProgress.takeTime = currentTakeTime;

                            double avgDownloadSpeed = (totalSize[0] / currentTakeTime) / 1024.0 / 1024.0; /*平均每毫秒下载速度mb*/
                            avgDownloadSpeed *= 1000;/*每秒下载速度mb*/

                            if (avgDownloadSpeed > maxAvgDownloadSpeed[0]) {
                                maxAvgDownloadSpeed[0] = avgDownloadSpeed;/*本次下载最高平均速度(理论值)*/
                            }
                            if (avgDownloadSpeed > serverMaxTransferSpeed) {
                                serverMaxTransferSpeed = avgDownloadSpeed;/*服务器最高传输速度*/
                            }

                            downloadProgress.avgDownloadSpeed = avgDownloadSpeed;

                            downloadProgress.maxDownloadSpeed = maxAvgDownloadSpeed[0];
                        }

                        if (percent - lastPercent[0] >= 5) { /*减少消息次数*/
                            lastPercent[0] = percent;

                            HttpFileServerHandler.this.sendDownloadProgress(downloadProgress);
                        }
                    }

                    totalSize[0] = total;
                }
            });

            ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); /*当发送完数据之后，由于采用的是Transfer-Encoding：chunk模式来传输数据，因此需要在发送一个长度为0的chunk用来标记数据传输完成*/
            if (!HttpHeaderUtil.isKeepAlive(request))
                lastContentFuture.addListener(ChannelFutureListener.CLOSE);
        } else if (request.method() == POST) { /*post*/
            logger.debug("## 请求：{}", request.uri());
            try {
                decoder = new HttpPostRequestDecoder(factory, request);
            } catch (HttpPostRequestDecoder.ErrorDataDecoderException e1) {
                e1.printStackTrace();
                /*todo 响应*/
                ctx.writeAndFlush(e1.getMessage());
                ctx.channel().close();
                return;
            }

            hasUploadData = isTransferEncodingChunked(request); /*这里可以看到是否含有上传文件部分*/

        }

    }

    private void doQrcode(ChannelHandlerContext ctx, FullHttpRequest request) throws IOException {
        RandomAccessFile randomAccessFile;
        File qrcodeImgFile = new File(HttpFileServerApp.QR_IMG_PATH);
        try {
            randomAccessFile = new RandomAccessFile(qrcodeImgFile, "r");
        } catch (FileNotFoundException fnfd) {
            sendError(ctx, HttpResponseStatus.NOT_FOUND);
            return;
        }

        long fileLength = randomAccessFile.length();
        HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
        HttpHeaderUtil.setContentLength(response, fileLength);
        setContentTypeHeader(response, qrcodeImgFile);
        ctx.write(response);

        ChannelFuture sendFileFuture = ctx.write(new ChunkedFile(randomAccessFile, 0, fileLength, 8192), ctx.newProgressivePromise());

        ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
        if (!HttpHeaderUtil.isKeepAlive(request))
            lastContentFuture.addListener(ChannelFutureListener.CLOSE);
    }

    /**
     * Example of reading request by chunk and getting values from chunk to chunk
     * 从decoder中读出数据，写入临时对象，然后写入...哪里？
     * 这个封装主要是为了释放临时对象
     */
    private void readHttpDataChunkByChunk() {
        try {
            while (decoder.hasNext()) {
                InterfaceHttpData data = decoder.next();
                if (data != null) {
                    try {
                        // new value
                        writeHttpData(data);
                    } finally {
                        data.release();
                    }
                }
            }
        } catch (HttpPostRequestDecoder.EndOfDataDecoderException e1) {
            // end
            //responseContent.append("\r\n\r\nEND OF CONTENT CHUNK BY CHUNK\r\n\r\n");
        }
    }

    private void reset() {
        /*request = null;*/

        // destroy the decoder to release all resources
        decoder.destroy();
        decoder = null;
    }

    private void writeHttpData(InterfaceHttpData data) {
        // Attribute就是form表单里带的各种 name= 的属性
        if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {
        } else if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.InternalAttribute) {
        } else {
            String uploadFileName = getUploadFileName(data);
            FileUpload fileUpload = (FileUpload) data;
            if (fileUpload.isCompleted()) {
                // fileUpload.isInMemory();// tells if the file is in Memory
                // or on File
                // fileUpload.renameTo(dest); // enable to move into another
                // File dest
                // decoder.removeFileUploadFromClean(fileUpload); //remove
                // the File of to delete file
                String saveDir = "/media/wei/upload";
                File dir = new File(saveDir + File.separator);
                if (!dir.exists()) {
                    dir.mkdir();
                }
                File dest = new File(dir, uploadFileName);
                try {
                    fileUpload.renameTo(dest);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 封装应答的回写
     *
     * @param ctx
     * @param message String的消息体Header中已经设置为Application/json
     */
    private final StringBuilder responseContent = new StringBuilder();

    private void writeResponseString(ChannelHandlerContext ctx, String message) {
        // Convert the response content to a ChannelBuffer.
        responseContent.setLength(0);
        responseContent.append(message);

        ByteBuf buf = copiedBuffer(responseContent.toString(), CharsetUtil.UTF_8);
        // Build the response object.
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf);

        response.headers().set("Content-Type", "application/json; charset=UTF-8");
        response.headers().set("Content-Length", buf.readableBytes() + "");

        // Write the response.
        ctx.channel().writeAndFlush(response);
    }

    private String getUploadFileName(InterfaceHttpData data) {
        String content = data.toString();
        String temp = content.substring(0, content.indexOf("\n"));
        content = temp.substring(temp.lastIndexOf("=") + 2, temp.lastIndexOf("\""));
        return content;
    }
    /*2017.12.4 上传文件 END*/


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        if (ctx.channel().isActive())
            sendError(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
    }

    private static final Pattern INSECURE_URI = Pattern.compile(".*[<>&\"].*");

    private String sanitizeUri(String uri) {
        if (uri.contains("%")) {
            try {
                uri = URLDecoder.decode(uri, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                try {
                    uri = URLDecoder.decode(uri, "ISO-8859-1");
                } catch (UnsupportedEncodingException e1) {
                    throw new Error();
                }
            }
        }

        logger.info("### uri:{}", uri);


        uri = uri.replace('/', File.separatorChar);
//        if (uri.contains(File.separator + '.') || uri.contains('.' + File.separator) || uri.startsWith(".") || uri.endsWith(".") || INSECURE_URI.matcher(uri).matches()) {
//            return null;
//        }
        return this.dir + uri;
    }

    private static final Pattern ALLOWED_FILE_NAME = Pattern.compile("[A-Za-z0-9][-_A-Za-z0-9\\.]*");

    private static void sendListing(ChannelHandlerContext ctx, File dir) throws IOException {
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=UTF-8");

        String dirPath = dir.getPath();
        StringBuilder buf = new StringBuilder();

        StringBuilder rows = new StringBuilder();
        if (pageHtml == null) {
            pageHtml = getPageHtml();
        }
        for (File f : dir.listFiles(pathname -> !pathname.getName().startsWith("."))) {
            String addRow = addRow(f.getName(), f.length(), f.isDirectory() ? 1 : 0, f.lastModified());
            rows.append(addRow);
        }

        String page = pageHtml.replace("__{DIR}__", dirPath).replace("__{ROWS}__", rows.toString()).replace("__{UPLOAD_HOST}__", HttpFileServer.IP);

        buf.append(page);

        ByteBuf buffer = copiedBuffer(buf, CharsetUtil.UTF_8);
        response.content().writeBytes(buffer);
        buffer.release();
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    private static String getPageHtml() throws IOException {
        if (pageHtml != null) {
            return pageHtml;
        }

        InputStream in = HttpFileServerHandler.class.getClassLoader().getResourceAsStream("file_explorer.html");

        byte[] data = FileUtil.read(in);
        if (data != null) {
            String content = new String(data);
            pageHtml = content;
            return content;
        }

        return "<ERROR>";
    }

    private static String addRow(String fileName, long bytesLength, int isDirectory, long lastModified) {
        double gb = (bytesLength / 1024.0 / 1024.0 / 1024.0);
        double mb = (bytesLength / 1024.0 / 1024.0);
        double kb = (bytesLength / 1024.0);


        String addRow =
                "<script>" +
                        "addRow('"
                        + fileName
                        + "', '" + fileName
                        + "', " + isDirectory
                        + ", " + bytesLength
                        + ", '" + (gb >= 1 ? CommonUtil.formatDouble(gb) + " GB'" : (mb >= 1 ? CommonUtil.formatDouble(mb) + " MB'" : (kb >= 1 ? CommonUtil.formatDouble(kb) + " KB'" : bytesLength + "B'")))
                        + ", 0, '" + CommonUtil.formateDate(lastModified) +
                        "')" +
                        ";</script>";
        return addRow;
    }


    private static void sendRedirect(ChannelHandlerContext ctx, String newUri) {
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.FOUND);
//        response.headers().set("LOCATIN", newUri);
        response.headers().set(HttpHeaderNames.LOCATION, newUri);
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status, String msg) {
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, copiedBuffer("Failure: " + status.toString() + "\r\n", CharsetUtil.UTF_8));
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=UTF-8");
        response.content().writeBytes(msg.getBytes());
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, copiedBuffer("Failure: " + status.toString() + "\r\n", CharsetUtil.UTF_8));
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=UTF-8");
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    private static void setContentTypeHeader(HttpResponse response, File file) {
        MimetypesFileTypeMap mimetypesFileTypeMap = new MimetypesFileTypeMap();
        if (file.getName().toLowerCase().endsWith(".mp4")) {
            response.headers().set("Content-Type", "video/mp4");
            /*response.headers().set("Content-Disposition", "inline; filename=\"effd27f7-5ca4-4e70-ad58-5487ced7fed8.mp4\"");*/
//            response.headers().set("Access-Control-Allow-Credentials", "true");
//            response.headers().set("Access-Control-Allow-Headers", "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type");
//            response.headers().set("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
//            response.headers().set("Access-Control-Allow-Origin", "*");
            return;
        }

        if (!file.getName().toLowerCase().endsWith(".jpg")
                && !file.getName().toLowerCase().endsWith(".jpeg")
                && !file.getName().toLowerCase().endsWith(".png")
                && !file.getName().toLowerCase().endsWith(".gif")
                ) {
            System.out.println("###file name:" + file.getName());
            /*加上此header 文件会被下载而不是浏览器打开查看*/
            response.headers().set("Content-Disposition", "attachment;filename=\"" + URLEncoder.encode(file.getName()) + "\""); /*指定文件名*/
        }

        response.headers().set(HttpHeaderNames.CONTENT_TYPE, mimetypesFileTypeMap.getContentType(file.getPath()));

    }

    private Map<String, String> getRequestParams(ChannelHandlerContext ctx, HttpRequest req) {

        Map<String, String> requestParams = new HashMap<>();
        /*get*/
        if (req.method() == GET) {
            QueryStringDecoder decoder = new QueryStringDecoder(req.uri());
            Map<String, List<String>> parame = decoder.parameters();
            Iterator<Map.Entry<String, List<String>>> iterator = parame.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<String, List<String>> next = iterator.next();
                requestParams.put(next.getKey(), next.getValue().get(0));
            }
        }
        /*post*/
        if (req.method() == POST) {
            HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(false), req);
            List<InterfaceHttpData> postData = decoder.getBodyHttpDatas(); //
            for (InterfaceHttpData data : postData) {
                if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {
                    MemoryAttribute attribute = (MemoryAttribute) data;
                    requestParams.put(attribute.getName(), attribute.getValue());
                }
            }
        }
        return requestParams;
    }


    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class DownloadProgress {
        private String userId, userName, userAvatar;
        private String fileName;
        private long startTime;
        private long takeTime;
        private long progressDataSize, totalDataSize;
        private double percent;
        private double avgDownloadSpeed;/*mb/s*/
        private double maxDownloadSpeed;/*mb/s*/
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class DownloadFileLog {
        private String userId, userName, userAvatar;
        private String fileName;
        private long startTime;
        private long takeTime;
        private long totalDataSize;
        private double avgDownloadSpeed;/*mb/s*/
        private double maxDownloadSpeed;/*mb/s*/
    }


    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class ServerData {
        private double totalTakeTime;
        private double totalDataSize;
        private double avgTransferSpeed;/*mb/s*/ /*总平均速度*/
        private double maxTransferSpeed;/*mb/s*/ /*最高速度*/
        private double maxAvgTransferSpeed;/*mb/s*/ /*最高平均速度（所有文件下载完成时平均速度中最高）*/
    }


    private void wsSend(Object obj) {
        logger.info("##send:{}", obj);
        /*if (HttpFileServer.wsClient != null) {
            HttpFileServer.wsClient.send(JSON.toJSONString(obj));
        } else {
            logger.error("##wsClient is null");
        }*/
        if (Application.wsClient != null && Application.wsClient.isAvaible()) {
            Application.wsClient.send(JSON.toJSONString(obj));
        } else {
            logger.error("##wsClient is null");
            if (!WSClient.isConnecting()) {
                try {
                    new WSClient();
                } catch (URISyntaxException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void sendDownloadProgress(DownloadProgress downloadProgress) {
        Map<String, Object> data = InstanceUtil.newHashMap();
        data.put("type", "download_progress");
        data.put("data", downloadProgress);
        wsSend(data);
    }

    private void sendDownloadComplete(DownloadFileLog downloadFileLog) {
        Map<String, Object> data = InstanceUtil.newHashMap();
        data.put("type", "download_complete");
        data.put("data", downloadFileLog);
        wsSend(data);
    }

    private void sendServerData(ServerData serverData) {
        Map<String, Object> data = InstanceUtil.newHashMap();
        data.put("type", "sever_data");
        data.put("data", serverData);
        wsSend(data);
    }
}